
// FuzzySystem has a fuzzifier, an inference system to query rules, and a defuzzifier
public class FuzzySystem {

  private Fuzzifier fuzz = null;
  private FuzzySetsManager setsManager = null;
  private Defuzzifier defuzz = null;
  private InferenceRule rule = null;

  public FuzzySystem() {
    fuzz = new Fuzzifier();
    setsManager = FuzzySetsManager.getInstance();
    defuzz = new Defuzzifier();
    rule = new InferenceRule();
  }

  private double min(double n1, double n2) {
    return n1 < n2 ? n1 : n2;
  }

  private double max(double n1, double n2) {
    return n1 > n2 ? n1 : n2;
  }

  // I do not like the fact that each time we need to new an array
  // some sort of container is probably better. the container should be flexible
  // (maybe a list? which is better? replying on GC and being stupid, or
  // doing some management ourselves?
  private double [] computeUnion(double qc1[], double lower1, 
                                 double qc2[], double lower2) {
    double upper1 = lower1 + qc1.length * MembershipFunction.RESOLUTION;
    double upper2 = lower2 + qc2.length * MembershipFunction.RESOLUTION;

    double min_bound = min(lower1, lower2);
    double max_bound = max(upper1, upper2);

    int num_grids = (int)((max_bound - min_bound) / MembershipFunction.RESOLUTION);

    //System.out.println("min: " + min_bound + " max: " + max_bound + " num grids: " + num_grids);
    double [] u_result = new double[num_grids];
    double x;
    int k;

    for (int i = 0; i < num_grids; i++) {
      x = min_bound + i * MembershipFunction.RESOLUTION;
      
      u_result[i] = 0;

      if (x >= lower1 && x < upper1) {
        k = (int)((x - lower1) / MembershipFunction.RESOLUTION);
        u_result[i] = max(u_result[i], qc1[k]); 
      }

      if (x >= lower2 && x < upper2) {
        k = (int)((x - lower2) / MembershipFunction.RESOLUTION);
        u_result[i] = max(u_result[i], qc2[k]);
      }
    }

    return u_result;
  }

  public double computeAcceleration(double velocity, double distance) {
    double [] distances = new double[5];
    double [] velocities = new double[5];

    FuzzySet A, D, V;
    FuzzySet [] setValues = FuzzySet.values();

    for (int i = 0; i < 5; i++) {
      D = setValues[i];
      V = setValues[5 + i];

      //System.out.println("D: " + D + "(" + distance + ") V: " + V + "(" + velocity + ")");
      distances[i] = setsManager.getDegreeOfValidity(D, distance);
      velocities[i] = setsManager.getDegreeOfValidity(V, velocity);

      //System.out.println("D: " + distances[i] + " V: " + velocities[i]);
    }
    
    // apply rules
    double lower = Double.MAX_VALUE, low;
    double [] result = null;
    double [] r;

    for (int i = 0; i < 5; i++) {
      D = setValues[i];
      for (int j = 0; j < 5; j++) {
        V = setValues[5 + j];
        A = rule.useRule(D, V);
        
        low = setsManager.getLowerBound(A);

        r = setsManager.getQualifiedConsequent(A, min(distances[i], velocities[j]));
        if (result == null) {
          result = r;
        } else {
          result = computeUnion(result, lower, r, low);
        }
        lower = min(lower, low);
      }
    }
    return defuzz.defuzzify(result, lower);
  }


  public static void main(String args[]) {
    FuzzySystem system = new FuzzySystem();
    double [] input1 = new double[100];
    double [] input2 = new double[50];

    for(int i=0; i< 50; i++) {
      input1[i] = 0.2;
      input2[i] = 0.5;
    }
    for(int i = 50; i < 100; i++) {
      input1[i] = 0.1;
    }

    double [] result = system.computeUnion(input1, 0, input2, 7.5);
    for(int i = 0; i < result.length; i++) {
      System.out.print(result[i]+" ");
    }
    System.out.println();

    double [] input3 = new double[50];
    result = system.computeUnion(result, 2, input3, 0);
    for(int i = 0; i < result.length; i++) {
      System.out.print(result[i]+" ");
    }
    System.out.println();
  }
}


